package info.fastpace.utils;

import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.concurrent.CancellationException;

public class CancelableCollection extends CancelableTask {
	private LinkedHashMap<CancelableTask, Integer> cancelables = new LinkedHashMap<CancelableTask, Integer>();
	private volatile boolean normalizing;
	private volatile State state = new State(null, 0, 0);
	private boolean isOneFailsAll = true;

	public boolean isOneFailsAll() {
		return isOneFailsAll;
	}

	public void setOneFailsAll(boolean isOneFailsAll) {
		this.isOneFailsAll = isOneFailsAll;
	}

	/**
	 * Adds and runs the cancelable
	 * 
	 * @param cancelable
	 * @return
	 * @throws Exception
	 *             if failed while running
	 * @throws CancellationException
	 *             if already canceled
	 */
	public void add(CancelableTask cancelable, int relativeTime) {
		if (cancelable == null) {
			return;
		}
		cancelables.put(cancelable, relativeTime);

		if (isCanceled()) {
			throw new CancellationException("Canceled while running!");
		}

		if (isDone()) {
			throw new RuntimeException("Cannot add tasks if the Cancelable collection is done running");
		}

		if (normalizing) {
			throw new RuntimeException("Cannot add tasks if already started normalizing");
		}
	}

	@Override
	protected void runImpl() throws Exception {
		normalizeProgress();
		for (Entry<CancelableTask, Integer> entry : cancelables.entrySet()) {
			int currentRelativeTime = entry.getValue();
			CancelableTask currentTask = entry.getKey();
			// Advance the state to the next one with the progress from the
			// previous one.
			state = new State(currentTask, currentRelativeTime, state.getProgress());
			try {
				currentTask.runUnsafe();
			} catch (Exception e) {
				if (isOneFailsAll) {
					throw e;
				}
				Config.getLog().i("Task failed but continueing with other tasks", e);
			}
		}
		state = new State(null, 0, 100);
	}

	private void normalizeProgress() {
		normalizing = true;
		int overall = 0;
		for (Entry<CancelableTask, Integer> entry : cancelables.entrySet()) {
			overall += entry.getValue();
		}
		double factor = overall == 0 ? 1 : (double) 100 / overall;
		for (Entry<CancelableTask, Integer> entry : cancelables.entrySet()) {
			entry.setValue((int) (entry.getValue() * factor));
		}
	}

	@Override
	protected void cancelImpl() {
		for (CancelableTask cancelable : cancelables.keySet()) {
			cancelable.cancel();
		}
	}

	@Override
	public int getProgress() {
		int progress = this.progress.get();
		int stateProgress = state.getProgress();
		this.progress.compareAndSet(progress, stateProgress); // Setting this
																// value just
																// for easier
																// debugging. I
																// has no
																// meaning in
																// CancelableCollection
		return super.getProgress();
	}

	private static class State {
		private CancelableTask currentTask;
		private int currentRelativeTime;
		private int progress;

		public State(CancelableTask currentTask, int currentRelativeTime, int progress) {
			super();
			this.currentTask = currentTask;
			this.currentRelativeTime = currentRelativeTime;
			this.progress = progress;
		}

		public int getProgress() {
			return progress + (currentTask == null ? 0 : (int) (currentTask.getProgress() * (double) currentRelativeTime / 100));
		}
	}
}
